#include "user.h"
#include "group.h"
#include "server.h"
#include <string>
#include <algorithm>


namespace mtm {
ostream& operator<<(ostream& os, const User& user) {
	os << '{' << user.firstName << ',' << user.lastName << ',';
	os << user.id << ',' << user.years << '}';
	return os;
}

shared_ptr<PowerUser> User::openNewGroup(const string& name) const {
	return Server::getInstance()->openNewGroup(id, name);
}

void User::joinNewGroup(const string& name) const {
	Server::getInstance()->requestToJoinGroup(id, name);
}

void User::leaveGroup(const string& name) const {
	Server::getInstance()->requestToLeaveGroup(id, name);
}

void User::postRecipe(const string& recipeName) {
	Server::getInstance()->validateUserGroup(id);
	shared_ptr < Recipe > recipe(new Recipe(id, recipeName));
	recipe = *recipes.insert(recipe).first;
	group.lock()->postRecipe(recipe);
	acquireRecipe(recipeName);
}

void User::joinRequestCallback(bool accepted, shared_ptr<Group> group) {
	if (accepted) {
		this->group = group;
	}
}

void User::leaveRequestCallback(bool accepted) {
	if (accepted) {
		group.reset();
	}
}

void User::acquireRecipe(const string& recipeName) {
	Server::getInstance()->validateUserGroup(id);
	auto recipe = group.lock()->getRecipe(recipeName);
	recipes.insert(recipe);
}

void User::forgetRecipe(unsigned chefId, const string& recipeName) {
	Server::getInstance()->validateUserGroup(id);
	if (id == chefId) {
		throw AppreciateYourOwnWorkException();
	}
	for (auto recipe : recipes) {
		if (recipe->chefId == chefId && recipe->name == recipeName) {
			recipes.erase(recipe);
			return;
		}
	}
	throw NoSuchRecipeInCollectionException();
}

int User::getDonation() const {
	int donation = 0;
	for (auto& recipe : recipes) {
		if (recipe->chefId == id) {
			donation++;
		}
	}
	return donation;
}

int User::getFollowersAmount() const {
	Server::getInstance()->validateUserGroup(id);
	int ret = 0;
	for (auto& recipe : recipes) {
		if (recipe->chefId == id) {
			ret += recipe.use_count();
		}
	}
	return ret;
}

shared_ptr<PowerUser> PowerUser::appointNewLeader() {
	Server::getInstance()->validateUserGroup(id);
	return group.lock()->appointNewLeader();
}

void PowerUser::inspectJoinRequest() {
	Server::getInstance()->validateUserGroup(id);
	auto user = joinRequestUser.lock();
	if (!user) {
		throw NoAwaitingJoinRequestException();
	}
	if (user->getYears() < years) {
		user->joinRequestCallback(false, group.lock());
	} else {
		group.lock()->users.push_back(user);
		user->joinRequestCallback(true, group.lock());
	}
	joinRequestUser.reset();
}

void PowerUser::inspectLeaveRequest(ostream& os) {
	Server::getInstance()->validateUserGroup(id);
	auto user = leaveRequestUser.lock();
	if (!user) {
		throw NoAwaitingLeaveRequestException();
	}
	if (user->getId() != id) {
		os << user->getFirstName() << ", you're no Jack Kennedy";
	}
	auto gr = group.lock();
	auto it = std::find(gr->users.begin(), gr->users.end(), user);
	gr->users.erase(it);
	user->leaveRequestCallback(true);
	leaveRequestUser.reset();
}

void PowerUser::leaveRequestCallback(bool accepted) {
	if (accepted && group.lock()->leader.get()==this) {
		appointNewLeader();
	}
	User::leaveRequestCallback(accepted);
}

}
